/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package cz.muni.fi.pb138.videodes.api.base;

import cz.muni.fi.pb138.videodes.api.exceptions.MissingDriverException;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.transform.OutputKeys;
import org.exist.xmldb.XQueryService;
import org.xmldb.api.base.Collection;
import org.xmldb.api.base.Resource;
import org.xmldb.api.base.ResourceIterator;
import org.xmldb.api.base.ResourceSet;
import org.xmldb.api.base.XMLDBException;
import org.xmldb.api.modules.XMLResource;
/**
 *
 * @author pseudo
 * 
 * This class isn't finished yet. But it's ready for usage with embedded databases
 * ToDo: support for connection pulling
 */
public class XMLDBTemplate {
    private static final Logger LOGGER = Logger.getLogger(XMLDBTemplate.class.getName());
    private static XMLDBDriver  driver = null;
    private static XQueryService service = null;
    
    /**
     * This method store resource into database under unique auto generated id
     * 
     * @param colname collection where resource will be store
     * @param content content of resource
     * @return id of stored resource
     */
    public static String storeResource(String colname, String content){
	XMLDBTemplate.isSet();
	Collection col = null;
	XMLResource document = null;
	try {
	    col = driver.getCollection(colname); 
	    document = (XMLResource) col.createResource(null, "XMLResource");
	    document.setContent(content);
	    col.storeResource(document);
	    return document.getId();
	} catch (XMLDBException ex) {
	    LOGGER.log(Level.SEVERE, "An error occurred while storing new resource", ex);
	}
	return null;
    }
    
    /**
     * This method update content of resource with given id
     * @param colname collection where resource is stored
     * @param content new content of resource
     * @param id id of resource
     * @return id of updated resource
     */
    public static String updateResource(String colname, String content, String id){
	XMLDBTemplate.isSet();
	Collection col = null;
	XMLResource document = null;
	try {
	    col = driver.getCollection(colname); 
            if (col.getResource(id) == null) {
                throw new IllegalArgumentException("Resource " + id + " not found in the database.");
            }
	    document = (XMLResource) col.createResource(id, "XMLResource");
	    document.setContent(content);
	    col.storeResource(document);
	    return document.getId();
	} catch (XMLDBException ex) {
	    LOGGER.log(Level.SEVERE, "An error occurred while storing new resource", ex);
	}
        
	return null;
    }
    
    /**
        /**
     * This method store resource into database under given id
     * 
     * @param colname collection where resource will be store
     * @param content content of resource
     * @param id
     * @return id of stored resource or null if resource store failed
     */
    public static String storeResource(String colname, File content, String id){
	XMLDBTemplate.isSet();
	Collection col = null;
	XMLResource document = null;
	
	if (!content.canRead()){
	    throw new IllegalArgumentException("Can not read file at " + content);
	}
	
	try {
	    col = driver.getCollection(colname); 
	    document = (XMLResource) col.createResource(id, "XMLResource");
	    document.setContent(content);
	    col.storeResource(document);
	    return document.getId();
	} catch (XMLDBException ex) {
	    LOGGER.log(Level.SEVERE, "An error occurred while storing new resource", ex);
	}
	return null;
    }
    
    /**
     * 
     * @param colname
     * @param id 
     */
     public static void removeResource(String colname, String id){
	XMLDBTemplate.isSet();
	Collection col = null;
	XMLResource document = null;
	try {
	    col = driver.getCollection(colname); 
	    document = (XMLResource) col.createResource(id, "XMLResource");
	    col.removeResource(document);
	} catch (XMLDBException ex) {
	    LOGGER.log(Level.SEVERE, "An error occurred while removing resource", ex);
	}
    }

    public static void executeQuery(String xquery, Object... params) {
        XMLDBTemplate.isSet();
        try {
            XMLDBTemplate.executeQueryIn(xquery, params);
        } catch (XMLDBException e) {
             LOGGER.log(Level.SEVERE, "Error when executing query", e);
        }
    }
    /**
     * This method execute given xquery expression and return it's results in list
     * @param <T>
     * @param xquery xquery expression 
     * @param mapper mapper for process raw xquery results
     * @param params parameters of xquery
     * @return Array of results
     */
    public static <T> List<T> executeQueryAsList(String xquery, RowMapper<T> mapper, Object... params) {
        XMLDBTemplate.isSet();
	List<T> ret = null;
        try {
            ret = XMLDBTemplate.executeQueryAsListIn(xquery, mapper, params);
        } catch (XMLDBException e) {
             LOGGER.log(Level.SEVERE, "Error when executing query with List return type", e);
        }
        return ret;
    }
 
    /**
     * This method execute given xquery expression and return it's as single value
     * @param <T>
     * @param xquery xquery expression 
     * @param mapper mapper for process raw xquery results
     * @param params parameters of xquery
     * @return Result of expression
     */
    public static <T> T executeQueryAsValue(String xquery, RowMapper<T> mapper, Object... params) {
        XMLDBTemplate.isSet();
	T ret = null;
        try {
            ret = XMLDBTemplate.executeQueryAsValueIn(xquery, mapper, params);
        } catch (XMLDBException e) {
             LOGGER.log(Level.SEVERE, "Error when executing query with List return type", e);
        }
        return ret;
    }    
    
    /**
     * 
     * @param xquery xquery expression 
     * @param params parameters of xquery
     * @throws XMLDBException 
     */
     private static void executeQueryIn(String xquery, Object params[]) throws XMLDBException {
        XMLDBTemplate.isSet();
	if (xquery == null) {
            throw new IllegalArgumentException("null argument xquery");
        }
	
        XPreparedStatement ps = new XPreparedStatement(xquery);
            if (params != null) {
                setParams(ps, params);
            }
	    
            service.query(ps.toString());
    }
       
  /**
     * This method execute given xquery expression and return it's results in list
     * @param <T>
     * @param xquery xquery expression 
     * @param mapper mapper for process raw xquery results
     * @param params parameters of xquery
     * @return Array of results
     * @throws XMLDBException 
     */
    private static <T> List<T> executeQueryAsListIn(String xquery, RowMapper<T> mapper, Object params[]) throws XMLDBException {
        XMLDBTemplate.isSet();
	if (xquery == null) {
            throw new IllegalArgumentException("null argument xquery");
        }
	
        XPreparedStatement ps = new XPreparedStatement(xquery);
        ResourceSet rs = null;
        List<T> ret = new ArrayList<T>();
            if (params != null) {
                setParams(ps, params);
            }
	    
            rs = service.query(ps.toString());
	    ResourceIterator i = rs.getIterator();
            while (i.hasMoreResources()) {
                Resource r = i.nextResource();
		ret.add(mapper.map(r));
            }
        return ret;
    }
    
 /**
     * This method execute given xquery expression and return it's as single value
     * @param <T>
     * @param xquery xquery expression 
     * @param mapper mapper for process raw xquery results
     * @param params parameters of xquery
     * @return Result of expression
     * @throws XMLDBException 
     */
   private static <T> T executeQueryAsValueIn(String xquery, RowMapper<T> mapper, Object params[]) throws XMLDBException {
        if (xquery == null) {
            throw new IllegalArgumentException("null argument xquery");
        }

        XPreparedStatement ps = new XPreparedStatement(xquery);
        ResourceSet rs = null;
        T ret = null;
	
        try {
            if (params != null) {
                setParams(ps, params);
            }
	    
            rs = service.query(ps.toString());
	    ResourceIterator i = rs.getIterator();
            while (i.hasMoreResources()) {
		Resource r = i.nextResource();
                ret = mapper.map(r);
            }

        } finally {
            close();
        }
        return ret;
    }
    /**
    * Set parameters of XPreparedStatement instance
    * @param ps XPreparedStatement of xquery expression
    * @param params Array of parameters
    * @throws XMLDBException 
    */
    private static void setParams(XPreparedStatement ps, Object params[]) throws XMLDBException {
	if (ps == null) {
            throw new IllegalArgumentException("null argument ps");
        }

        for (int i = 0; i < params.length;) {
            Object param = params[i];
            if (param == null) {
                throw new UnsupportedOperationException("Null value isn't suported");
            } else if (param instanceof String) {
                ps.setString(++i, (String) param);
            } else if (param instanceof Double) {
                ps.setDouble(++i, ((Double) param).doubleValue());
	    } else if (param instanceof Integer) {
                ps.setInt(++i, ((Integer) param).intValue());
            } else if (param instanceof Long) {
                ps.setLong(++i, ((Long) param).longValue());
            } else {
                throw new UnsupportedOperationException("Unsupported type" + param.getClass());
            }
        }
    }
    
    /**
     * Open connection to database
     */
    public static void open(){
	try {
	    driver.open();
	    service = driver.getService();
	    service.setProperty(OutputKeys.INDENT, "yes");
	    service.setProperty(OutputKeys.ENCODING, "UTF-8");
	} catch (XMLDBException ex) {
	    LOGGER.log(Level.SEVERE, "Error occurred when setting driver and xquery service", ex);
	}
    }
    
    /**
     * close connection with database
     */
   public static void close(){
	driver.close();
    }
    
   /**
    * test if class is set properly
    */
    private static void isSet(){
	if (driver == null || service == null){
	    throw new MissingDriverException();
	}
    }
    
    /**
     * Set a driver of database 
     * @param driver Driver of database
     */
    public static void setDriver(XMLDBDriver driver){
	if (driver == null){
	    throw new IllegalArgumentException("driver can not be null");
	}
	
	XMLDBTemplate.driver = driver;
    }
}
